package com.jd.finance.oss.util;

import com.alibaba.fastjson.JSON;
import com.aliyun.oss.HttpMethod;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.CopyObjectResult;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.PutObjectResult;
import com.jd.finance.oss.config.OSSConfig;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.activation.MimetypesFileTypeMap;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.util.Date;
import java.util.Iterator;

/**
 * 阿里云对象存储工具类
 */
@Slf4j
@Component
public class AliyunOSSUtils {

    private static OSS ossClient;

    private static OSSConfig ossConfig;

    //使用非静态方法注入到静态属性变量中
    @Autowired
    public void setOssConfig(OSSConfig ossConfig) {
        AliyunOSSUtils.ossConfig = ossConfig;
    }

    /**
     * 对象上传
     *
     * @param multipartFile 文件输入流
     * @param objectKey     对象标识，即对象在bucket中唯一key,可以是文件名，如：temp/img.jpg
     * @return boolean 上传成功-true，上传失败-false
     */
    public static Object[] uploadObject(MultipartFile multipartFile, String objectKey) {
        log.info("阿里云配置：" + JSON.toJSONString(ossConfig));
        if (ifCompress(multipartFile)) {
            log.info("图片压缩处理，fileName = " + multipartFile.getOriginalFilename());
            return uploadCompress(multipartFile, objectKey);
        } else {
            log.info("图片未压缩处理，fileName = " + multipartFile.getOriginalFilename());
            return new Object[]{upload(multipartFile, objectKey), multipartFile.getSize()};
        }
    }

    public static boolean ifCompress(MultipartFile multipartFile) {
        try {
            if (ossConfig.getImageCompressEnabled() && multipartFile.getContentType().startsWith("image") && multipartFile.getSize() >= 1048576) {
                log.info("图片压缩处理，fileName = " + multipartFile.getOriginalFilename());
                return true;
            } else {
                log.info("图片未压缩处理，fileName = " + multipartFile.getOriginalFilename());
                return false;
            }
        } catch (Exception e) {
            log.error("图片压缩处理ifCompress", e);
            return false;

        }
    }

    public static Object[] uploadCompress(MultipartFile multipartFile, String objectKey) {
        long time = System.currentTimeMillis();

        ByteArrayInputStream inputStream = null;
        byte[] bytes = null;
        BufferedImage originalImage = null;
        try {
            originalImage = ImageIO.read(multipartFile.getInputStream());
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            Thumbnails.of(originalImage)
                    .scale(initScale()).outputFormat("jpeg").toOutputStream(outputStream);

            bytes = outputStream.toByteArray();
            inputStream = new ByteArrayInputStream(bytes);

        } catch (Exception e) {
            log.error("图片压缩异常，使用未压缩上传", e);
            return new Object[]{upload(multipartFile, objectKey), multipartFile.getSize()};
        } finally {
            if (originalImage != null) {
                originalImage.getGraphics().dispose();
            }
        }
        long time1 = new Date().getTime();
        log.info("uploadCompressTime:{}", time1 - time);


        OSS ossClient = null;
        try {
            // 创建OSSClient实例。
            ossClient = new OSSClientBuilder().build(getEndpointUrl(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());

            // 流式上传对象
            PutObjectResult putObjectResult = ossClient.putObject(ossConfig.getBucketName(), objectKey, inputStream);
            return new Object[]{true, Long.valueOf(bytes.length + "")};
        } catch (Exception e) {
            log.error("图片压缩异常，使用未压缩上传", e);
            return new Object[]{upload(multipartFile, objectKey), multipartFile.getSize()};
        } finally {
            // 关闭OSSClient
            if (ossClient != null) {
                ossClient.shutdown();
            }
            long time2 = new Date().getTime();
            log.info("uploadCompressTime:OssClient:{}", time2 - time1);
            log.info("uploadCompressTime:OssClientALL:{}", time2 - time);
        }


    }

    private static double initScale() {
        double scale = 0.7;
        try {
            BigDecimal scaleDecimal = new BigDecimal(ossConfig.getImageCompressScale());
            scale = scaleDecimal.doubleValue();
        } catch (Exception e) {
            log.error("图片压缩精度配置异常，scale=" + ossConfig.getImageCompressScale(), e);
        }
        return scale;
    }

    public static boolean upload(MultipartFile multipartFile, String objectKey) {
        try {
            return upload(multipartFile.getInputStream(), objectKey);
        } catch (IOException e) {
            log.error("无法获取文件", e);
            return false;
        }
    }

    /**
     * 对象上传
     *
     * @param inputStream 文件输入流
     * @param objectKey   对象标识，即对象在bucket中唯一key,可以是文件名，如：temp/img.jpg
     * @return boolean 上传成功-true，上传失败-false
     */
    public static boolean upload(InputStream inputStream, String objectKey) {
        OSS ossClient = null;
        try {
            // 创建OSSClient实例。
            ossClient = new OSSClientBuilder().build(getEndpointUrl(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());
            // 流式上传对象
            ossClient.putObject(ossConfig.getBucketName(), objectKey, inputStream);

            return true;
        } catch (Exception e) {
            log.error("对象上传异常", e);
            //System.out.println("对象上传异常");
            return false;
        } finally {
            // 关闭OSSClient
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * 对象下载
     *
     * @param objectKey    对象标识，即在bucket中的唯一key
     * @param outputStream 输出流
     * @return true 下载成功，false 下载失败
     */
    public static boolean downloadObject(String objectKey, OutputStream outputStream) {
        OSS ossClient = null;
        //创建输入流（读文件）输出流（写文件）
        BufferedInputStream inputStream = null;

        try {
            // 创建OSSClient实例。
            ossClient = new OSSClientBuilder().build(getEndpointUrl(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());

            //对象不存在
            boolean doesObjectExist = ossClient.doesObjectExist(ossConfig.getBucketName(), objectKey);
            if (!doesObjectExist) {
                log.error("对象下载失败，阿里云OSS对象不存在, objectKey = " + objectKey);
                return false;
            }

            // 获取对象
            OSSObject ossObject = ossClient.getObject(ossConfig.getBucketName(), objectKey);

            // 获取对象输入流
            inputStream = new BufferedInputStream(ossObject.getObjectContent());
            int len = 0;
            byte[] buff = new byte[1024];// 用来存储每次读取到的字节数组

            //对接输入与输出流
            while ((len = inputStream.read(buff)) != -1) {
                outputStream.write(buff, 0, len);
                outputStream.flush();
            }

            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    log.error("对象下载时，输出流关闭异常", e);
                }
            }
            return true;

        } catch (Exception e) {
            log.error("对象下载异常", e);
            return false;
        } finally {
            //关闭流
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error("对象下载时，读入流关闭异常", e);
                }
            }
            /*if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    log.error("对象下载时，输出流关闭异常",e);
                }
            }*/
            // 关闭OSSClient
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * 删除对象
     *
     * @param objectKey 对象标识，即在bucket中的唯一key
     * @return boolean 删除成功-true，删除失败-false
     */
    public static boolean deleteObject(String objectKey) {
        OSS ossClient = null;
        try {
            // 创建OSSClient实例。
            ossClient = new OSSClientBuilder().build(getEndpointUrl(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());

            //对象不存在
            boolean doesObjectExist = ossClient.doesObjectExist(ossConfig.getBucketName(), objectKey);
            if (!doesObjectExist) {
                log.error("对象删除失败，阿里云OSS对象不存在, objectKey = " + objectKey);
                return true;
            }

            // 删除文件或目录。如果要删除目录，目录必须为空。
            ossClient.deleteObject(ossConfig.getBucketName(), objectKey);

            return true;

        } catch (Exception e) {
            log.error("对象删除异常", e);
            //System.out.println("对象删除异常");
            return false;
        } finally {
            // 关闭OSSClient
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * 获取对象的URL，用于预览
     *
     * @param objectKey 对象存储的key
     * @return URL 对象签名URL
     */
    public static URL getPreviewUrl(String objectKey) {
        if (objectKey == null) {
            return null;
        }
        try {
            // 客户端
            OSS ossClient = new OSSClientBuilder().build(getEndpointUrl(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());

            //对象不存在
            boolean doesObjectExist = ossClient.doesObjectExist(ossConfig.getBucketName(), objectKey);
            if (!doesObjectExist) {
                log.info("获取预览URL签名失败，阿里云OSS对象不存在, objectKey = " + objectKey);
                return null;
            }

            // URL签名请求
            GeneratePresignedUrlRequest req =
                    new GeneratePresignedUrlRequest(ossConfig.getBucketName(), objectKey, HttpMethod.GET);

            // 指定签名URL过期时间
            Date expiration = new Date(new Date().getTime() + ossConfig.getExpiration());
            req.setExpiration(expiration);

            // 获取处理后图片的URL
            URL url = ossClient.generatePresignedUrl(req);

            return url;
        } catch (Exception e) {
            log.error("获取预览URL签名异常", e);
            return null;
        }
    }

    /**
     * 获取缩放后的图片URL，用于缩略图
     *
     * @param objectKey 对象存储的key
     * @return String 处理后的对象签名url
     */
    public static URL getResizePreviewUrl(String objectKey) {
        if (objectKey == null) {
            return null;
        }
        try {
            // 客户端
            OSS ossClient = new OSSClientBuilder().build(getEndpointUrl(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());

            //对象不存在
            boolean doesObjectExist = ossClient.doesObjectExist(ossConfig.getBucketName(), objectKey);
            if (!doesObjectExist) {
                log.info("获取缩略预览URL签名失败，阿里云OSS对象不存在, objectKey = " + objectKey);
                return null;
            }

            // URL签名请求
            GeneratePresignedUrlRequest req =
                    new GeneratePresignedUrlRequest(ossConfig.getBucketName(), objectKey, HttpMethod.GET);

            // 指定签名URL过期时间
            Date expiration = new Date(new Date().getTime() + ossConfig.getExpiration());
            req.setExpiration(expiration);

            // 将图片缩放为固定宽300 px。
            String style = "image/resize,m_fixed,w_300";
            req.setProcess(style);

            // 获取处理后图片的URL
            URL url = ossClient.generatePresignedUrl(req);
            return url;
        } catch (Exception e) {
            log.error("获取缩略图URL签名异常", e);
            return null;
        }
    }

    /**
     * 同一bucket内，对象拷贝
     *
     * @param objectKey    对象存储的key
     * @param newObjectKey 对象存储的key
     * @return String 处理后的对象签名url
     */
    public static OSSObject copyObject(String objectKey, String newObjectKey) {
        if (objectKey == null || newObjectKey == null) {
            return null;
        }
        if (objectKey.startsWith("/")) {
            objectKey = objectKey.replaceFirst("/", "");
        }
        try {
            // 客户端
            OSS ossClient = new OSSClientBuilder().build(getEndpointUrl(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());

            //对象复制
            CopyObjectResult copyObjectResult = ossClient.copyObject(ossConfig.getBucketName(), objectKey, ossConfig.getBucketName(), newObjectKey);

            //获取文件
            OSSObject object = ossClient.getObject(ossConfig.getBucketName(), newObjectKey);

            return object;
        } catch (Exception e) {
            log.error("文件对象拷贝异常", e);
            return null;
        }
    }

    /**
     * 拼接阿里云oss完整url
     *
     * @param objectKey
     * @return https://bucketName.endpoint/objectname
     */
    public static String getObjectUrl(String objectKey) {
        return "https://" + ossConfig.getBucketName() + "." + ossConfig.getEndpoint() + "/" + objectKey;
    }

    /**
     * 拼接阿里云oss endpoint
     *
     * @return http://endpoint
     */
    public static String getEndpointUrl() {
        return "http://" + ossConfig.getEndpoint();
    }

    /**
     * 关闭阿里云OSS客户端
     */
    public static void close() {
        if (ossClient != null) {
            ossClient.shutdown();
        }
    }

    /**
     * URL获取输入流
     *
     * @param url
     * @return
     * @throws IOException
     */
    public static InputStream openConnection(URL url) throws IOException {
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        //连接超时机制，防止阻塞
        if (ossConfig != null && ossConfig.getConnectTimeout() != null) {
            conn.setConnectTimeout(ossConfig.getConnectTimeout().intValue());
        } else {
            conn.setConnectTimeout(10 * 1000);
        }
        InputStream in = conn.getInputStream();//通过输入流获取图片数据
        return in;
    }

    /**
     * 读取文件，获取图片类型
     *
     * @param url
     * @return JPEG png gif null
     */
    public static String getImageFormat(URL url) {
        try {
            InputStream in = openConnection(url);
            // Create an image input stream on the image
            ImageInputStream iis = ImageIO.createImageInputStream(in);

            // Find all image readers that recognize the image format
            Iterator<ImageReader> iter = ImageIO.getImageReaders(iis);
            if (!iter.hasNext()) {
                // No readers found
                return null;
            }

            // Use the first reader
            ImageReader reader = iter.next();

            // Close stream
            iis.close();

            // Return the format name
            return reader.getFormatName();
        } catch (IOException e) {
            // The image could not be read
            return null;
        }
    }

    /**
     * 获取文件对应的Content-Type
     *
     * @param file
     * @return
     * @throws Exception
     */
    public static String getContentType(File file) {
        //利用nio提供的类判断文件ContentType
        String contentType = null;
        try {
            contentType = Files.probeContentType(file.toPath());//请求类型，如 image/jpeg，image/png 等，与文件一一对应

        } catch (Exception e) {

        }

        //若失败则调用另一个方法进行判断
        if (contentType == null) {
            try {
                contentType = new MimetypesFileTypeMap().getContentType(file);//请求类型，如 image/jpeg，image/png 等，与文件一一对应
            } catch (Exception e) {

            }
        }
        //若失败则返回默认值
        if (contentType == null) {
            contentType = "application/octet-stream";
        }
        return contentType;
    }

}
